using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.Net.Sockets;
using System.Net;

namespace rudp
{
    public class Server
    {
        private ManualResetEventSlim                   dataAvailable;
        private Thread                                 workerThread;
        private Socket                                 socket;
        private ConcurrentQueue<RawMessage>    rawMessages;

        public Server()
        {
        }

        public void Initialize()
        {
            this.dataAvailable = new ManualResetEventSlim ();
            this.rawMessages = new ConcurrentQueue<RawMessage>();

            ThreadStart threadStart = new ThreadStart (this.ProcessQueue);

            this.workerThread = new Thread (threadStart);
            this.workerThread.Start ();

            this.socket = new Socket (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            this.socket.Bind(new IPEndPoint(IPAddress.Any, 5000));

            this.Receive(this.socket);
        }

        private void Receive( Socket socket )
        {
            byte[] buffer = new byte[1400];
            EndPoint remoteHost = new IPEndPoint(IPAddress.Any, 0);

            State state = new State
            {
                ServerSocket = socket,
                Buffer =  buffer,
            };

            socket.BeginReceiveFrom( buffer, 0, buffer.Length, SocketFlags.None, ref remoteHost, this.OnReceive, state);
        }

        private void OnReceive( IAsyncResult result )
        {
            State state = (State)result.AsyncState;
            EndPoint remoteHost = new IPEndPoint(IPAddress.Any, 0);

            state.ServerSocket.EndReceiveFrom(result, ref remoteHost);

            this.Receive(state.ServerSocket);

            RawMessage rawMessage = new RawMessage
            {
                Buffer =  state.Buffer,
                RemoteHost = (IPEndPoint)remoteHost
            };

            this.rawMessages.Enqueue(rawMessage);
            this.dataAvailable.Set();
        }

        private void ProcessQueue()
        {
            Dictionary<int, ReceivedMessages> receiveStates = new Dictionary<int, ReceivedMessages>();

            for(;;)
            {
                this.dataAvailable.Wait();

                while (this.rawMessages.Count > 0)
                {
                    RawMessage receivedMessage;

                    this.rawMessages.TryDequeue(out receivedMessage);

                    Message message = new Message();
                    message.FromByteArray(receivedMessage.Buffer);

                    int remotePort = receivedMessage.RemoteHost.Port;

                    ReceivedMessages receivedMessages;

                    if (!receiveStates.ContainsKey(remotePort))
                    {
                        receivedMessages = new ReceivedMessages{
                            FirstReceived = DateTime.Now,
                            Messages = new Message[10]
                        };
                        receiveStates.Add(receivedMessage.RemoteHost.Port, receivedMessages);
                    }
                    else
                        receivedMessages = receiveStates[remotePort];

                    // The messages array length is preallocated to the max length
                    // of the message chunks. We can quickly determine the missing 
                    // messages by what items in the array are null
                    receivedMessages.Messages[message.Index] = message;    

                    this.HandleMessages(receivedMessages, message);                             
                }

                this.dataAvailable.Reset();                  
            }
        }

        private void HandleMessages( ReceivedMessages receivedMessages, Message latestMessage )
        {
            receivedMessages.LastReceived = DateTime.Now;

            // If this is a receipt request then the index of the message is the number of
            // messages that we should have
            if ((latestMessage.Flags & MessageFlags.ReceiptRequest) == MessageFlags.ReceiptRequest)
            {
                int requiredMessages = latestMessage.Index;

                List<int> missingMessages = new List<int>();

                // Check to see if we have the required messages
                for (int index = 0; index <= requiredMessages; index++)
                {
                    if (receivedMessages.Messages[index].Data == null )
                        missingMessages.Add(index);
                }
            }

        }
    }
}

